#include <climits>
#include <cstring>
#include <limits>
#include <string>

extern "C" {
#include "sc_ini.h"
} // extern "C"

namespace iniconf {

enum Code {
    Ok = 0,

    // sc_ini_parse_file 在 IO 错误时返回 -1。
    IOError = 1,
    SyntaxError,
    InvalidInteger,
    UndefinedField,
};

class Visitor {
public:
    virtual Code Visit(int line,
        char const* section,
        char const* key, char const* value)
        = 0;
};

static int OnItem(void* arg, int line,
    char const* section,
    char const* key, char const* value)
{
    return -((Visitor*)arg)->Visit(line, section, key, value);
}

struct Error {
    int line;
    char const* key;
    char const* value;
};

Error* GetError()
{
    thread_local Error err {
        .line = 0,
        .key = nullptr,
        .value = nullptr,
    };
    return &err;
}

template <typename T>
Code Parse(char const* text, T* result) = delete;

template <>
Code Parse<std::string>(char const* text, std::string* result)
{
    *result = text;
    return Code::Ok;
}

template <>
Code Parse<long long>(char const* text, long long* result)
{
    char* endptr;
    long long tmp = std::strtoll(text, &endptr, 10);

    if (*endptr != '\0') {
        return iniconf::InvalidInteger;
    }

    *result = tmp;
    return Code::Ok;
}

template <>
Code Parse<int>(char const* text, int* result)
{
    long long tmp;
    auto code = Parse(text, &tmp);
    if (code != Code::Ok) {
        return code;
    }

    if (tmp < std::numeric_limits<int>::min() || std::numeric_limits<int>::max() < tmp) {
        return Code::InvalidInteger;
    }

    *result = tmp;

    return code;
}

static inline bool StrEq(char const* lhs, char const* rhs)
{
    return strcmp(lhs, rhs) == 0;
}

#define INICONF_XM_PARSE_FIELD(TYPE, NAME, DEFAULT)        \
    if (::iniconf::StrEq(key, #NAME)) {                    \
        auto code = ::iniconf::Parse(value, &self_->NAME); \
                                                           \
        if (code != ::iniconf::Code::Ok) {                 \
            ::iniconf::GetError()->line = line;            \
            ::iniconf::GetError()->key = key;              \
            ::iniconf::GetError()->value = value;          \
        }                                                  \
                                                           \
        return code;                                       \
    }

#define INICONF_DERIVE_LOAD_INI(NAME, XM_CONF)                          \
    iniconf::Code LoadINI(char const* text)                             \
    {                                                                   \
        class INIVisitor : public iniconf::Visitor {                    \
        public:                                                         \
            explicit INIVisitor(NAME* self)                             \
                : self_(self)                                           \
            {                                                           \
            }                                                           \
                                                                        \
            iniconf::Code Visit(int line, char const* section,          \
                char const* key, char const* value)                     \
                override                                                \
            {                                                           \
                XM_CONF(INICONF_XM_PARSE_FIELD)                         \
                return iniconf::UndefinedField;                         \
            }                                                           \
                                                                        \
        private:                                                        \
            NAME* self_;                                                \
        };                                                              \
                                                                        \
        INIVisitor visitor(this);                                       \
                                                                        \
        int r = ::sc_ini_parse_string(&visitor, iniconf::OnItem, text); \
        if (r != 0) {                                                   \
            if (r > 0) {                                                \
                ::iniconf::GetError()->line = r;                        \
                return ::iniconf::Code::SyntaxError;                    \
            }                                                           \
            return (::iniconf::Code)(-r);                               \
        }                                                               \
        return ::iniconf::Code::Ok;                                     \
    }

} // namespace iniconf

namespace sample {

#define FOO_XM(_)            \
    _(std::string, name, "") \
    _(int, age, 0)

struct Foo {

#define _(TYPE, NAME, DEFAULT) TYPE NAME = DEFAULT;
    FOO_XM(_)
#undef _

    INICONF_DERIVE_LOAD_INI(Foo, FOO_XM);
};

} // namespace sample

int main()
{
    sample::Foo foo;
    foo.LoadINI("[foo]\nage = 10\nname = Alice\n");

    printf("age  = %d\n", foo.age);
    printf("name = %s\n", foo.name.c_str());
    return 0;
}
